Array measurements ROBAT 226.238:ΒΆ

  • 5 mic array adafruit 5 i2s
  • 48khz
  • 1.5 meters distance (~ far field = 10π›Œ; π›Œmax = fmin; 343/2 = 171 Hz --> 10π›Œ = 1715 Hz; at 1000Hz: 4.5π›Œ = 4.5*0.343 = 1.5435 m)
  • measured clockwise (R: 0-180; L: 180-360)
  • array rotated around the central mic kept a 1.5m from source

HW settings:

  • Harman kandon AWR 445 vol = -25 db (front: 270 to 90)
  • Harman kandon AWR 445 vol = -5 db (back: 100 to 260)
  • fireface analog out 1/2 stereo vol = 0db
  • tweeter #1
  • Ref mic: gras +30 db fireface channel 9, +20db channel A power module

NOTE:

InΒ [5]:
from IPython.display import Image
from IPython.display import display

display(
    Image(filename="./PXL_20250509_10061094.jpg", width=300),
    Image(filename="./PXL_20250509_10062195.jpg", width=300),
    Image(filename="./rme802_matrix.png", width=700),
    Image(filename="./rme802_mixer.png", width=700)
)
No description has been provided for this image
No description has been provided for this image
No description has been provided for this image
No description has been provided for this image

Output signalΒΆ

Out signal used during the recordings: 1-40Khz 5ms sweep.

InΒ [6]:
import scipy.signal as signal 
import numpy as np 
import matplotlib.pyplot as plt 
import sounddevice as sd
import soundfile as sf

#%%
# make a sweep
durns = np.array([3, 4, 5, 8, 10] )*1e-3
fs = 48000 # Hz

chirp = []
all_sweeps = []
for durn in durns:
    t = np.linspace(0, durn, int(fs*durn))
    start_f, end_f = 1e3, 20e3
    sweep = signal.chirp(t, start_f, t[-1], end_f)
    sweep *= signal.windows.tukey(sweep.size, 0.95)
    sweep *= 0.8
    sweep_padded = np.pad(sweep, pad_width=[int(fs*0.1)]*2, constant_values=[0,0])
    all_sweeps.append(sweep_padded)
    chirp.append(sweep)
    

sweeps_combined = np.concatenate(all_sweeps)

# Read the saved WAV file
out_signal, _ = sf.read('1_20k_5sweeps.wav')

# Plot the time-domain signal and spectrogram
plt.figure(figsize=(30, 6))

# Time-domain plot
plt.subplot(2, 1, 1)
plt.plot(np.linspace(0, len(out_signal) / fs, len(out_signal)), out_signal)
plt.title('Output Signal')
plt.xlabel('Time [s]')
plt.ylabel('Amplitude')

# Spectrogram plot
plt.subplot(2, 1, 2, sharex=plt.gca())
plt.specgram(out_signal, Fs=fs, NFFT=512, noverlap=256, cmap='viridis')
plt.title('Spectrogram of the Output Signal')
plt.xlabel('Time [s]')
plt.ylabel('Frequency [Hz]')
plt.ylim(0, 25e3)

# plot the chirp
plt.figure(figsize=(15, 15))
for i, sweep in enumerate(chirp):
    plt.subplot(len(chirp), 2, 2 * i + 1)
    plt.plot(np.linspace(0, len(sweep) / fs, len(sweep)), sweep)
    plt.title(f'Sweep {i+1}')
    plt.xlabel('Time [s]')
    plt.ylabel('Amplitude')
    # Spectrogram plot

    plt.subplot(len(chirp), 2, 2 * i + 2)
    plt.specgram(sweep, Fs=fs, NFFT=32, noverlap=16, cmap='viridis')
    plt.title('Spectrogram of the Output Signal')
    plt.xlabel('Time [s]')
    plt.ylabel('Frequency [Hz]')
    plt.ylim(0, 25e3)

plt.tight_layout()
plt.show()
No description has been provided for this image
No description has been provided for this image

AnalysisΒΆ

  1. Angular audiofile import and extraction from multiwav to single channel recordings
InΒ [7]:
# %% Libraries and files
import os
import soundfile as sf

# Load audio files, then plot a 6x6 grid
DIR = "./2025-05-09/original/"  # Directory containing the audio files
audio_files = os.listdir(DIR)  # List all files in the sweeps directory
audio_files.sort()  # Sort the files in ascending order

# Directory to save the extracted channels
output_dir = "./2025-05-09/extracted_channels/"
os.makedirs(output_dir, exist_ok=True)  # Create the directory if it doesn't exist

# Path to the multi-channel WAV file
for file in audio_files:
    file_path = os.path.join(DIR, file)

    angle_name = file.split('.')[0]
    print(f"Processing file: {angle_name}")

    # Read the multi-channel WAV file
    audio_data, sample_rate = sf.read(DIR + file)

    # Check the shape of the audio data
    print(f"Audio data shape: {audio_data.shape}")  # (samples, channels)

    # Extract individual channels
    num_channels = audio_data.shape[1]  # Number of channels
    channels = [audio_data[:, i] for i in range(num_channels)]

    # Save each channel as a separate WAV file
    for i, channel_data in enumerate(channels):
        output_file = os.path.join(output_dir, angle_name+f"_{i + 1}.wav")  # Path to the output file
        sf.write(output_file, channel_data, sample_rate)
        print(f"Saved channel {i + 1} to {output_file}")
Processing file: 000
Audio data shape: (454134, 5)
Saved channel 1 to ./2025-05-09/extracted_channels/000_1.wav
Saved channel 2 to ./2025-05-09/extracted_channels/000_2.wav
Saved channel 3 to ./2025-05-09/extracted_channels/000_3.wav
Saved channel 4 to ./2025-05-09/extracted_channels/000_4.wav
Saved channel 5 to ./2025-05-09/extracted_channels/000_5.wav
Processing file: 010
Audio data shape: (380350, 5)
Saved channel 1 to ./2025-05-09/extracted_channels/010_1.wav
Saved channel 2 to ./2025-05-09/extracted_channels/010_2.wav
Saved channel 3 to ./2025-05-09/extracted_channels/010_3.wav
Saved channel 4 to ./2025-05-09/extracted_channels/010_4.wav
Saved channel 5 to ./2025-05-09/extracted_channels/010_5.wav
Processing file: 020
Audio data shape: (419955, 5)
Saved channel 1 to ./2025-05-09/extracted_channels/020_1.wav
Saved channel 2 to ./2025-05-09/extracted_channels/020_2.wav
Saved channel 3 to ./2025-05-09/extracted_channels/020_3.wav
Saved channel 4 to ./2025-05-09/extracted_channels/020_4.wav
Saved channel 5 to ./2025-05-09/extracted_channels/020_5.wav
Processing file: 030
Audio data shape: (353658, 5)
Saved channel 1 to ./2025-05-09/extracted_channels/030_1.wav
Saved channel 2 to ./2025-05-09/extracted_channels/030_2.wav
Saved channel 3 to ./2025-05-09/extracted_channels/030_3.wav
Saved channel 4 to ./2025-05-09/extracted_channels/030_4.wav
Saved channel 5 to ./2025-05-09/extracted_channels/030_5.wav
Processing file: 040
Audio data shape: (213962, 5)
Saved channel 1 to ./2025-05-09/extracted_channels/040_1.wav
Saved channel 2 to ./2025-05-09/extracted_channels/040_2.wav
Saved channel 3 to ./2025-05-09/extracted_channels/040_3.wav
Saved channel 4 to ./2025-05-09/extracted_channels/040_4.wav
Saved channel 5 to ./2025-05-09/extracted_channels/040_5.wav
Processing file: 050
Audio data shape: (221450, 5)
Saved channel 1 to ./2025-05-09/extracted_channels/050_1.wav
Saved channel 2 to ./2025-05-09/extracted_channels/050_2.wav
Saved channel 3 to ./2025-05-09/extracted_channels/050_3.wav
Saved channel 4 to ./2025-05-09/extracted_channels/050_4.wav
Saved channel 5 to ./2025-05-09/extracted_channels/050_5.wav
Processing file: 060
Audio data shape: (181846, 5)
Saved channel 1 to ./2025-05-09/extracted_channels/060_1.wav
Saved channel 2 to ./2025-05-09/extracted_channels/060_2.wav
Saved channel 3 to ./2025-05-09/extracted_channels/060_3.wav
Saved channel 4 to ./2025-05-09/extracted_channels/060_4.wav
Saved channel 5 to ./2025-05-09/extracted_channels/060_5.wav
Processing file: 070
Audio data shape: (184774, 5)
Saved channel 1 to ./2025-05-09/extracted_channels/070_1.wav
Saved channel 2 to ./2025-05-09/extracted_channels/070_2.wav
Saved channel 3 to ./2025-05-09/extracted_channels/070_3.wav
Saved channel 4 to ./2025-05-09/extracted_channels/070_4.wav
Saved channel 5 to ./2025-05-09/extracted_channels/070_5.wav
Processing file: 080
Audio data shape: (232731, 5)
Saved channel 1 to ./2025-05-09/extracted_channels/080_1.wav
Saved channel 2 to ./2025-05-09/extracted_channels/080_2.wav
Saved channel 3 to ./2025-05-09/extracted_channels/080_3.wav
Saved channel 4 to ./2025-05-09/extracted_channels/080_4.wav
Saved channel 5 to ./2025-05-09/extracted_channels/080_5.wav
Processing file: 090
Audio data shape: (253998, 5)
Saved channel 1 to ./2025-05-09/extracted_channels/090_1.wav
Saved channel 2 to ./2025-05-09/extracted_channels/090_2.wav
Saved channel 3 to ./2025-05-09/extracted_channels/090_3.wav
Saved channel 4 to ./2025-05-09/extracted_channels/090_4.wav
Saved channel 5 to ./2025-05-09/extracted_channels/090_5.wav
Processing file: 100
Audio data shape: (213961, 5)
Saved channel 1 to ./2025-05-09/extracted_channels/100_1.wav
Saved channel 2 to ./2025-05-09/extracted_channels/100_2.wav
Saved channel 3 to ./2025-05-09/extracted_channels/100_3.wav
Saved channel 4 to ./2025-05-09/extracted_channels/100_4.wav
Saved channel 5 to ./2025-05-09/extracted_channels/100_5.wav
Processing file: 110
Audio data shape: (313189, 5)
Saved channel 1 to ./2025-05-09/extracted_channels/110_1.wav
Saved channel 2 to ./2025-05-09/extracted_channels/110_2.wav
Saved channel 3 to ./2025-05-09/extracted_channels/110_3.wav
Saved channel 4 to ./2025-05-09/extracted_channels/110_4.wav
Saved channel 5 to ./2025-05-09/extracted_channels/110_5.wav
Processing file: 120
Audio data shape: (313621, 5)
Saved channel 1 to ./2025-05-09/extracted_channels/120_1.wav
Saved channel 2 to ./2025-05-09/extracted_channels/120_2.wav
Saved channel 3 to ./2025-05-09/extracted_channels/120_3.wav
Saved channel 4 to ./2025-05-09/extracted_channels/120_4.wav
Saved channel 5 to ./2025-05-09/extracted_channels/120_5.wav
Processing file: 130
Audio data shape: (203928, 5)
Saved channel 1 to ./2025-05-09/extracted_channels/130_1.wav
Saved channel 2 to ./2025-05-09/extracted_channels/130_2.wav
Saved channel 3 to ./2025-05-09/extracted_channels/130_3.wav
Saved channel 4 to ./2025-05-09/extracted_channels/130_4.wav
Saved channel 5 to ./2025-05-09/extracted_channels/130_5.wav
Processing file: 140
Audio data shape: (215210, 5)
Saved channel 1 to ./2025-05-09/extracted_channels/140_1.wav
Saved channel 2 to ./2025-05-09/extracted_channels/140_2.wav
Saved channel 3 to ./2025-05-09/extracted_channels/140_3.wav
Saved channel 4 to ./2025-05-09/extracted_channels/140_4.wav
Saved channel 5 to ./2025-05-09/extracted_channels/140_5.wav
Processing file: 150
Audio data shape: (210601, 5)
Saved channel 1 to ./2025-05-09/extracted_channels/150_1.wav
Saved channel 2 to ./2025-05-09/extracted_channels/150_2.wav
Saved channel 3 to ./2025-05-09/extracted_channels/150_3.wav
Saved channel 4 to ./2025-05-09/extracted_channels/150_4.wav
Saved channel 5 to ./2025-05-09/extracted_channels/150_5.wav
Processing file: 160
Audio data shape: (249822, 5)
Saved channel 1 to ./2025-05-09/extracted_channels/160_1.wav
Saved channel 2 to ./2025-05-09/extracted_channels/160_2.wav
Saved channel 3 to ./2025-05-09/extracted_channels/160_3.wav
Saved channel 4 to ./2025-05-09/extracted_channels/160_4.wav
Saved channel 5 to ./2025-05-09/extracted_channels/160_5.wav
Processing file: 170
Audio data shape: (236476, 5)
Saved channel 1 to ./2025-05-09/extracted_channels/170_1.wav
Saved channel 2 to ./2025-05-09/extracted_channels/170_2.wav
Saved channel 3 to ./2025-05-09/extracted_channels/170_3.wav
Saved channel 4 to ./2025-05-09/extracted_channels/170_4.wav
Saved channel 5 to ./2025-05-09/extracted_channels/170_5.wav
Processing file: 180
Audio data shape: (216026, 5)
Saved channel 1 to ./2025-05-09/extracted_channels/180_1.wav
Saved channel 2 to ./2025-05-09/extracted_channels/180_2.wav
Saved channel 3 to ./2025-05-09/extracted_channels/180_3.wav
Saved channel 4 to ./2025-05-09/extracted_channels/180_4.wav
Saved channel 5 to ./2025-05-09/extracted_channels/180_5.wav
Processing file: 190
Audio data shape: (247325, 5)
Saved channel 1 to ./2025-05-09/extracted_channels/190_1.wav
Saved channel 2 to ./2025-05-09/extracted_channels/190_2.wav
Saved channel 3 to ./2025-05-09/extracted_channels/190_3.wav
Saved channel 4 to ./2025-05-09/extracted_channels/190_4.wav
Saved channel 5 to ./2025-05-09/extracted_channels/190_5.wav
Processing file: 200
Audio data shape: (191447, 5)
Saved channel 1 to ./2025-05-09/extracted_channels/200_1.wav
Saved channel 2 to ./2025-05-09/extracted_channels/200_2.wav
Saved channel 3 to ./2025-05-09/extracted_channels/200_3.wav
Saved channel 4 to ./2025-05-09/extracted_channels/200_4.wav
Saved channel 5 to ./2025-05-09/extracted_channels/200_5.wav
Processing file: 210
Audio data shape: (198935, 5)
Saved channel 1 to ./2025-05-09/extracted_channels/210_1.wav
Saved channel 2 to ./2025-05-09/extracted_channels/210_2.wav
Saved channel 3 to ./2025-05-09/extracted_channels/210_3.wav
Saved channel 4 to ./2025-05-09/extracted_channels/210_4.wav
Saved channel 5 to ./2025-05-09/extracted_channels/210_5.wav
Processing file: 220
Audio data shape: (261088, 5)
Saved channel 1 to ./2025-05-09/extracted_channels/220_1.wav
Saved channel 2 to ./2025-05-09/extracted_channels/220_2.wav
Saved channel 3 to ./2025-05-09/extracted_channels/220_3.wav
Saved channel 4 to ./2025-05-09/extracted_channels/220_4.wav
Saved channel 5 to ./2025-05-09/extracted_channels/220_5.wav
Processing file: 230
Audio data shape: (204361, 5)
Saved channel 1 to ./2025-05-09/extracted_channels/230_1.wav
Saved channel 2 to ./2025-05-09/extracted_channels/230_2.wav
Saved channel 3 to ./2025-05-09/extracted_channels/230_3.wav
Saved channel 4 to ./2025-05-09/extracted_channels/230_4.wav
Saved channel 5 to ./2025-05-09/extracted_channels/230_5.wav
Processing file: 240
Audio data shape: (187255, 5)
Saved channel 1 to ./2025-05-09/extracted_channels/240_1.wav
Saved channel 2 to ./2025-05-09/extracted_channels/240_2.wav
Saved channel 3 to ./2025-05-09/extracted_channels/240_3.wav
Saved channel 4 to ./2025-05-09/extracted_channels/240_4.wav
Saved channel 5 to ./2025-05-09/extracted_channels/240_5.wav
Processing file: 250
Audio data shape: (215210, 5)
Saved channel 1 to ./2025-05-09/extracted_channels/250_1.wav
Saved channel 2 to ./2025-05-09/extracted_channels/250_2.wav
Saved channel 3 to ./2025-05-09/extracted_channels/250_3.wav
Saved channel 4 to ./2025-05-09/extracted_channels/250_4.wav
Saved channel 5 to ./2025-05-09/extracted_channels/250_5.wav
Processing file: 260
Audio data shape: (256062, 5)
Saved channel 1 to ./2025-05-09/extracted_channels/260_1.wav
Saved channel 2 to ./2025-05-09/extracted_channels/260_2.wav
Saved channel 3 to ./2025-05-09/extracted_channels/260_3.wav
Saved channel 4 to ./2025-05-09/extracted_channels/260_4.wav
Saved channel 5 to ./2025-05-09/extracted_channels/260_5.wav
Processing file: 270
Audio data shape: (232731, 5)
Saved channel 1 to ./2025-05-09/extracted_channels/270_1.wav
Saved channel 2 to ./2025-05-09/extracted_channels/270_2.wav
Saved channel 3 to ./2025-05-09/extracted_channels/270_3.wav
Saved channel 4 to ./2025-05-09/extracted_channels/270_4.wav
Saved channel 5 to ./2025-05-09/extracted_channels/270_5.wav
Processing file: 280
Audio data shape: (237725, 5)
Saved channel 1 to ./2025-05-09/extracted_channels/280_1.wav
Saved channel 2 to ./2025-05-09/extracted_channels/280_2.wav
Saved channel 3 to ./2025-05-09/extracted_channels/280_3.wav
Saved channel 4 to ./2025-05-09/extracted_channels/280_4.wav
Saved channel 5 to ./2025-05-09/extracted_channels/280_5.wav
Processing file: 290
Audio data shape: (227307, 5)
Saved channel 1 to ./2025-05-09/extracted_channels/290_1.wav
Saved channel 2 to ./2025-05-09/extracted_channels/290_2.wav
Saved channel 3 to ./2025-05-09/extracted_channels/290_3.wav
Saved channel 4 to ./2025-05-09/extracted_channels/290_4.wav
Saved channel 5 to ./2025-05-09/extracted_channels/290_5.wav
Processing file: 300
Audio data shape: (200183, 5)
Saved channel 1 to ./2025-05-09/extracted_channels/300_1.wav
Saved channel 2 to ./2025-05-09/extracted_channels/300_2.wav
Saved channel 3 to ./2025-05-09/extracted_channels/300_3.wav
Saved channel 4 to ./2025-05-09/extracted_channels/300_4.wav
Saved channel 5 to ./2025-05-09/extracted_channels/300_5.wav
Processing file: 310
Audio data shape: (207289, 5)
Saved channel 1 to ./2025-05-09/extracted_channels/310_1.wav
Saved channel 2 to ./2025-05-09/extracted_channels/310_2.wav
Saved channel 3 to ./2025-05-09/extracted_channels/310_3.wav
Saved channel 4 to ./2025-05-09/extracted_channels/310_4.wav
Saved channel 5 to ./2025-05-09/extracted_channels/310_5.wav
Processing file: 320
Audio data shape: (197256, 5)
Saved channel 1 to ./2025-05-09/extracted_channels/320_1.wav
Saved channel 2 to ./2025-05-09/extracted_channels/320_2.wav
Saved channel 3 to ./2025-05-09/extracted_channels/320_3.wav
Saved channel 4 to ./2025-05-09/extracted_channels/320_4.wav
Saved channel 5 to ./2025-05-09/extracted_channels/320_5.wav
Processing file: 330
Audio data shape: (221882, 5)
Saved channel 1 to ./2025-05-09/extracted_channels/330_1.wav
Saved channel 2 to ./2025-05-09/extracted_channels/330_2.wav
Saved channel 3 to ./2025-05-09/extracted_channels/330_3.wav
Saved channel 4 to ./2025-05-09/extracted_channels/330_4.wav
Saved channel 5 to ./2025-05-09/extracted_channels/330_5.wav
Processing file: 340
Audio data shape: (206857, 5)
Saved channel 1 to ./2025-05-09/extracted_channels/340_1.wav
Saved channel 2 to ./2025-05-09/extracted_channels/340_2.wav
Saved channel 3 to ./2025-05-09/extracted_channels/340_3.wav
Saved channel 4 to ./2025-05-09/extracted_channels/340_4.wav
Saved channel 5 to ./2025-05-09/extracted_channels/340_5.wav
Processing file: 350
Audio data shape: (267344, 5)
Saved channel 1 to ./2025-05-09/extracted_channels/350_1.wav
Saved channel 2 to ./2025-05-09/extracted_channels/350_2.wav
Saved channel 3 to ./2025-05-09/extracted_channels/350_3.wav
Saved channel 4 to ./2025-05-09/extracted_channels/350_4.wav
Saved channel 5 to ./2025-05-09/extracted_channels/350_5.wav
Processing file: 360
Audio data shape: (454134, 5)
Saved channel 1 to ./2025-05-09/extracted_channels/360_1.wav
Saved channel 2 to ./2025-05-09/extracted_channels/360_2.wav
Saved channel 3 to ./2025-05-09/extracted_channels/360_3.wav
Saved channel 4 to ./2025-05-09/extracted_channels/360_4.wav
Saved channel 5 to ./2025-05-09/extracted_channels/360_5.wav
  1. Sorting of the angular extracted files into the separate channels
InΒ [8]:
# List all extracted channel files separated by channel number
from natsort import natsorted

# Directory containing the extracted channels
extracted_channels_dir = output_dir

# List all extracted channel files
channel_files = os.listdir(extracted_channels_dir)

# Filter out directories and non-audio files, keep only valid files
channel_files = [f for f in channel_files if os.path.isfile(os.path.join(extracted_channels_dir, f)) and f.endswith('.wav')]

# Sort the files naturally by the last part of their names (e.g., channel number)
sorted_channel_files = natsorted(channel_files, key=lambda x: int(x.split('_')[-1].split('.')[0]))

# Group files by the last part of their name (channel number)
grouped_files = {}

for file in sorted_channel_files:
    # Extract the channel number from the file name (e.g., "350_1.wav" -> "1")
    channel_number = int(file.split('_')[-1].split('.')[0])

    # Group files by channel number
    if channel_number not in grouped_files:
        grouped_files[channel_number] = []
    grouped_files[channel_number].append(file)

for i in range(len(grouped_files)):
    grouped_files[i+1].sort()

# Print grouped files
for channel_number, files in grouped_files.items():
    print(f"Channel {channel_number}:")
    for f in files:
        print(f"  {f}")
Channel 1:
  000_1.wav
  010_1.wav
  020_1.wav
  030_1.wav
  040_1.wav
  050_1.wav
  060_1.wav
  070_1.wav
  080_1.wav
  090_1.wav
  100_1.wav
  110_1.wav
  120_1.wav
  130_1.wav
  140_1.wav
  150_1.wav
  160_1.wav
  170_1.wav
  180_1.wav
  190_1.wav
  200_1.wav
  210_1.wav
  220_1.wav
  230_1.wav
  240_1.wav
  250_1.wav
  260_1.wav
  270_1.wav
  280_1.wav
  290_1.wav
  300_1.wav
  310_1.wav
  320_1.wav
  330_1.wav
  340_1.wav
  350_1.wav
  360_1.wav
Channel 2:
  000_2.wav
  010_2.wav
  020_2.wav
  030_2.wav
  040_2.wav
  050_2.wav
  060_2.wav
  070_2.wav
  080_2.wav
  090_2.wav
  100_2.wav
  110_2.wav
  120_2.wav
  130_2.wav
  140_2.wav
  150_2.wav
  160_2.wav
  170_2.wav
  180_2.wav
  190_2.wav
  200_2.wav
  210_2.wav
  220_2.wav
  230_2.wav
  240_2.wav
  250_2.wav
  260_2.wav
  270_2.wav
  280_2.wav
  290_2.wav
  300_2.wav
  310_2.wav
  320_2.wav
  330_2.wav
  340_2.wav
  350_2.wav
  360_2.wav
Channel 3:
  000_3.wav
  010_3.wav
  020_3.wav
  030_3.wav
  040_3.wav
  050_3.wav
  060_3.wav
  070_3.wav
  080_3.wav
  090_3.wav
  100_3.wav
  110_3.wav
  120_3.wav
  130_3.wav
  140_3.wav
  150_3.wav
  160_3.wav
  170_3.wav
  180_3.wav
  190_3.wav
  200_3.wav
  210_3.wav
  220_3.wav
  230_3.wav
  240_3.wav
  250_3.wav
  260_3.wav
  270_3.wav
  280_3.wav
  290_3.wav
  300_3.wav
  310_3.wav
  320_3.wav
  330_3.wav
  340_3.wav
  350_3.wav
  360_3.wav
Channel 4:
  000_4.wav
  010_4.wav
  020_4.wav
  030_4.wav
  040_4.wav
  050_4.wav
  060_4.wav
  070_4.wav
  080_4.wav
  090_4.wav
  100_4.wav
  110_4.wav
  120_4.wav
  130_4.wav
  140_4.wav
  150_4.wav
  160_4.wav
  170_4.wav
  180_4.wav
  190_4.wav
  200_4.wav
  210_4.wav
  220_4.wav
  230_4.wav
  240_4.wav
  250_4.wav
  260_4.wav
  270_4.wav
  280_4.wav
  290_4.wav
  300_4.wav
  310_4.wav
  320_4.wav
  330_4.wav
  340_4.wav
  350_4.wav
  360_4.wav
Channel 5:
  000_5.wav
  010_5.wav
  020_5.wav
  030_5.wav
  040_5.wav
  050_5.wav
  060_5.wav
  070_5.wav
  080_5.wav
  090_5.wav
  100_5.wav
  110_5.wav
  120_5.wav
  130_5.wav
  140_5.wav
  150_5.wav
  160_5.wav
  170_5.wav
  180_5.wav
  190_5.wav
  200_5.wav
  210_5.wav
  220_5.wav
  230_5.wav
  240_5.wav
  250_5.wav
  260_5.wav
  270_5.wav
  280_5.wav
  290_5.wav
  300_5.wav
  310_5.wav
  320_5.wav
  330_5.wav
  340_5.wav
  350_5.wav
  360_5.wav
  1. Matched filtering:
  • Every file is matched with the output
  • First sweep is extracted and displayed
  • Detected sweeps over a threshold value set in the peakfinding to avoid misvalues
  • First sweep is saved to a new directory to be analyzed
  • avarage RMS of all the sweep in every recording is saved
InΒ [15]:
import scipy
import scipy.stats

gain = 20  # dB

db_to_linear = lambda X: 10**(X/20)
dB = lambda X: 20 * np.log10(abs(np.array(X).flatten()))

# Define the matched filter function
def matched_filter(recording, chirp_template):
    filtered_output = np.roll(signal.correlate(recording, chirp_template, 'same', method='direct'), -len(chirp_template)//2)
    filtered_output *= signal.windows.tukey(filtered_output.size, 0.1)
    filtered_envelope = np.abs(signal.hilbert(filtered_output))
    #return filtered_output
    return filtered_envelope

# Detect peaks in the matched filter output
def detect_peaks(filtered_output):
    peaks, properties = signal.find_peaks(filtered_output, prominence=0.1, distance=0.2 * sample_rate)
    return peaks

# Process each channel
DIR_first_sweep = extracted_channels_dir + "first_sweep/"  # Directory to save the first sweeps
os.makedirs(DIR_first_sweep, exist_ok=True)  # Create the directory if it doesn't exist

# Dictionary to store RMS values for all files
rms_values_dict = {}

channel_number = 1
for i in range(len(grouped_files)):
    files = grouped_files[i+1]
    print(f"Processing Channel {channel_number}:")
    
    # Create a new figure for each channel
    fig, ax = plt.subplots(figsize=(15, 5))
    ax.set_title(f"Channel {channel_number}", fontsize=20)
    ax.set_xlabel("Seconds")
    ax.set_ylabel("Amplitude")
    ax.grid(True)

    for file in files[0:len(files)]:
        file_path = os.path.join(extracted_channels_dir, file)
        recording, sample_rate = sf.read(file_path)

        angle_name = int(file.split('_')[0])

        if 100 <= angle_name <= 260:  
            
            recording *= db_to_linear(-gain)
            #print('file',file, '\n',recording)

        # Apply matched filtering
        filtered_output = matched_filter(recording, chirp[0])
        
        ax.plot(np.linspace(0, len(filtered_output) / sample_rate, len(filtered_output)), filtered_output, label=f"Filtered Output - {file}")
        #ax.plot(np.linspace(0, len(filtered_envelope) / sample_rate, len(filtered_envelope)), filtered_envelope, label=f"Filtered env - {file}", color='orange')
        ax.plot(np.linspace(0, len(recording) / sample_rate, len(recording)), recording, label=f"rec - {file}")
        #ax.legend(loc='upper right')
        #ax.set_xlim(4.334, 4.338)
        #ax.set_xlim(4, 6)


        # Detect peaks
        peaks = detect_peaks(filtered_output)
        #print(f"Peaks detected in {file}: {len(peaks)}")
        
        # Plot peaks on the filtered output
        ax.plot(peaks / sample_rate, filtered_output[peaks], "x", label="Detected Peaks")
        #ax.legend(loc='upper right')
        
        if len(peaks) > 0:
            # Extract the first sweep
            first_sweep_start = peaks[0] 
            first_sweep_end = first_sweep_start + len(chirp[0])
            first_sweep = recording[first_sweep_start:first_sweep_end]

            # Calculate RMS value of the first sweep
            rms_first_sweep = np.sqrt(np.mean(first_sweep**2))
            #print(f"RMS value of the first sweep in {file}: {rms_first_sweep:.5f}")

            # Store RMS value in the dictionary
            #rms_values_dict[file] = rms_first_sweep

            # Calculate RMS values for all detected peaks
            rms_values = []
            for idx_peak, peak in enumerate(peaks):

                sweep_start = peak 
                sweep_end = sweep_start + len(chirp[idx_peak]) 
                sweep = recording[sweep_start:sweep_end]
                rms = np.sqrt(np.mean(sweep**2))
                rms_values.append(rms)
                #print(f"RMS value of sweep at peak {peak/sample_rate} sec in {file}: {rms:.5f}")
            
            # Calculate the average RMS value of all peaks
            average_rms = scipy.stats.mode(rms_values)
            average_rms = average_rms[0]
            rms_values_dict[file] = average_rms

            #print(f"Average RMS value of all sweeps in {file}: {average_rms:.5f}")

            sf.write(DIR_first_sweep + file, first_sweep, int(sample_rate))

        else:
            print(f"No sweeps detected in {file} - Channel {channel_number}")
        

    # Plot all angles, skipping '360'
    fig1, axs = plt.subplots(9, 4, figsize=(15, 30), sharey=False)
    angles = [file.split('_')[0] for file in files]  # Extract angle names from filenames

    idx_to_plot = 0
    for idx, file in enumerate(files):
        if angles[idx] == '360':
            continue  # Skip the 360 angle

        file_path = os.path.join(DIR_first_sweep, file)
        audio, sample_rate = sf.read(file_path)

        rms = np.sqrt(np.mean(audio**2))
        rms_db = 20 * np.log10(rms)
        
        row = idx_to_plot // 4
        col = idx_to_plot % 4
        
        ax = axs[row, col]
        ax.plot(np.linspace(0, len(audio) / sample_rate, len(audio)), audio)
        ax.set_title(f"Angle: {angles[idx]} degrees ")  # Use extracted angle name with units
        ax.set_xlabel("Time (s)")
        ax.set_ylabel("Amplitude")
        ax.grid(True)
        ax.legend([f'RMS: {rms:.5f}\nRMS: {rms_db:.5f} dB'], loc='upper left')

        idx_to_plot += 1

    #plt.suptitle(f"Channel {channel_number}: First Sweep for Each Angle", fontsize=20)
    plt.tight_layout()  # Adjust layout to make room for suptitle
    plt.show(block = False)

    ax.legend()
    channel_number += 1

plt.show(block = False)

# Print the dictionary of RMS values
print("\nRMS Values for All Files:")
for file, rms_value in rms_values_dict.items():
    print(f"{file}: {rms_value:.5f}")
Processing Channel 1:
No description has been provided for this image
No description has been provided for this image
No artists with labels found to put in legend.  Note that artists whose label start with an underscore are ignored when legend() is called with no argument.
Processing Channel 2:
No description has been provided for this image
No description has been provided for this image
No artists with labels found to put in legend.  Note that artists whose label start with an underscore are ignored when legend() is called with no argument.
Processing Channel 3:
No description has been provided for this image
No description has been provided for this image
No artists with labels found to put in legend.  Note that artists whose label start with an underscore are ignored when legend() is called with no argument.
Processing Channel 4:
No description has been provided for this image
No description has been provided for this image
No artists with labels found to put in legend.  Note that artists whose label start with an underscore are ignored when legend() is called with no argument.
Processing Channel 5:
No description has been provided for this image
No description has been provided for this image
No artists with labels found to put in legend.  Note that artists whose label start with an underscore are ignored when legend() is called with no argument.
RMS Values for All Files:
000_1.wav: 0.15046
010_1.wav: 0.16014
020_1.wav: 0.17951
030_1.wav: 0.18979
040_1.wav: 0.16510
050_1.wav: 0.14863
060_1.wav: 0.12735
070_1.wav: 0.11317
080_1.wav: 0.10198
090_1.wav: 0.08756
100_1.wav: 0.01701
110_1.wav: 0.01478
120_1.wav: 0.01297
130_1.wav: 0.01261
140_1.wav: 0.01147
150_1.wav: 0.01118
160_1.wav: 0.01183
170_1.wav: 0.01040
180_1.wav: 0.00757
190_1.wav: 0.01014
200_1.wav: 0.01358
210_1.wav: 0.01526
220_1.wav: 0.01439
230_1.wav: 0.01484
240_1.wav: 0.01517
250_1.wav: 0.01681
260_1.wav: 0.01851
270_1.wav: 0.10171
280_1.wav: 0.09637
290_1.wav: 0.10343
300_1.wav: 0.10931
310_1.wav: 0.11664
320_1.wav: 0.12209
330_1.wav: 0.12596
340_1.wav: 0.13108
350_1.wav: 0.14367
360_1.wav: 0.15046
000_2.wav: 0.19680
010_2.wav: 0.21200
020_2.wav: 0.20799
030_2.wav: 0.18566
040_2.wav: 0.16460
050_2.wav: 0.15166
060_2.wav: 0.12622
070_2.wav: 0.11355
080_2.wav: 0.09845
090_2.wav: 0.09308
100_2.wav: 0.01553
110_2.wav: 0.01537
120_2.wav: 0.01560
130_2.wav: 0.01080
140_2.wav: 0.01130
150_2.wav: 0.01136
160_2.wav: 0.01108
170_2.wav: 0.01131
180_2.wav: 0.01027
190_2.wav: 0.00914
200_2.wav: 0.00950
210_2.wav: 0.01299
220_2.wav: 0.01657
230_2.wav: 0.01729
240_2.wav: 0.01472
250_2.wav: 0.01537
260_2.wav: 0.01639
270_2.wav: 0.10224
280_2.wav: 0.11581
290_2.wav: 0.12986
300_2.wav: 0.13933
310_2.wav: 0.14755
320_2.wav: 0.15251
330_2.wav: 0.14870
340_2.wav: 0.15802
350_2.wav: 0.17754
360_2.wav: 0.19680
000_3.wav: 0.19127
010_3.wav: 0.17870
020_3.wav: 0.16991
030_3.wav: 0.16008
040_3.wav: 0.15698
050_3.wav: 0.15000
060_3.wav: 0.13254
070_3.wav: 0.11037
080_3.wav: 0.09134
090_3.wav: 0.08066
100_3.wav: 0.01610
110_3.wav: 0.01567
120_3.wav: 0.01480
130_3.wav: 0.01316
140_3.wav: 0.01053
150_3.wav: 0.00681
160_3.wav: 0.00977
170_3.wav: 0.00992
180_3.wav: 0.01047
190_3.wav: 0.00776
200_3.wav: 0.00892
210_3.wav: 0.00878
220_3.wav: 0.00981
230_3.wav: 0.01365
240_3.wav: 0.01508
250_3.wav: 0.01517
260_3.wav: 0.01478
270_3.wav: 0.08787
280_3.wav: 0.09639
290_3.wav: 0.11300
300_3.wav: 0.12706
310_3.wav: 0.13780
320_3.wav: 0.14654
330_3.wav: 0.16125
340_3.wav: 0.17975
350_3.wav: 0.19574
360_3.wav: 0.19127
000_4.wav: 0.16978
010_4.wav: 0.15868
020_4.wav: 0.16228
030_4.wav: 0.16705
040_4.wav: 0.16555
050_4.wav: 0.15696
060_4.wav: 0.14435
070_4.wav: 0.12380
080_4.wav: 0.11383
090_4.wav: 0.10061
100_4.wav: 0.01583
110_4.wav: 0.01424
120_4.wav: 0.01327
130_4.wav: 0.01566
140_4.wav: 0.01517
150_4.wav: 0.01330
160_4.wav: 0.00955
170_4.wav: 0.00754
180_4.wav: 0.00752
190_4.wav: 0.01049
200_4.wav: 0.00824
210_4.wav: 0.00908
220_4.wav: 0.00982
230_4.wav: 0.01039
240_4.wav: 0.01300
250_4.wav: 0.01472
260_4.wav: 0.01502
270_4.wav: 0.09169
280_4.wav: 0.10145
290_4.wav: 0.11476
300_4.wav: 0.12400
310_4.wav: 0.13412
320_4.wav: 0.15292
330_4.wav: 0.18093
340_4.wav: 0.19255
350_4.wav: 0.18037
360_4.wav: 0.16978
000_5.wav: 0.13540
010_5.wav: 0.12874
020_5.wav: 0.13312
030_5.wav: 0.13537
040_5.wav: 0.11921
050_5.wav: 0.13755
060_5.wav: 0.13343
070_5.wav: 0.12294
080_5.wav: 0.11394
090_5.wav: 0.10543
100_5.wav: 0.01841
110_5.wav: 0.01707
120_5.wav: 0.01502
130_5.wav: 0.01388
140_5.wav: 0.01317
150_5.wav: 0.01080
160_5.wav: 0.01281
170_5.wav: 0.01159
180_5.wav: 0.00915
190_5.wav: 0.00853
200_5.wav: 0.00800
210_5.wav: 0.01078
220_5.wav: 0.00925
230_5.wav: 0.01041
240_5.wav: 0.01169
250_5.wav: 0.01328
260_5.wav: 0.01464
270_5.wav: 0.09456
280_5.wav: 0.10749
290_5.wav: 0.11737
300_5.wav: 0.12439
310_5.wav: 0.13717
320_5.wav: 0.15770
330_5.wav: 0.16257
340_5.wav: 0.13906
350_5.wav: 0.14876
360_5.wav: 0.13540
  1. RMS values of the whole file is calculated and displayed in a polar plot
InΒ [10]:
# RMS values of the overall recording for each channel and each angle

num_channels = len(grouped_files)
fig_polar, axs_polar = plt.subplots(num_channels, 1, figsize=( 5,25), subplot_kw={'projection': 'polar'})
fig_polar.suptitle("RMS Values of Overall Recording for Each Channel", fontsize=16)

fig_linear, ax_linear = plt.subplots(figsize=(10, 10))
fig_linear.suptitle("RMS Values of Overall Recording for All Channels", fontsize=16)

for i in range(num_channels):
    channel_number = i + 1
    files = grouped_files[channel_number]
    
    rms_values = []
    rms_values_norm_db = []
    angles = []
    
    for file in files:

        rms = rms_values_dict[file]
        
        rms_values.append(rms)

        rms_values_norm = rms_values / rms_values[0]
        rms_values_norm_db = 20 * np.log10(rms_values_norm)

        angle_name = file.split('_')[0]
        angles.append(int(angle_name))
    
    # Convert angles to radians
    angles_rad = np.radians(angles)
    
    # Plot RMS values in polar plot
    ax_polar = axs_polar[i] if num_channels > 1 else axs_polar
    ax_polar.plot(angles_rad, rms_values_norm_db, linestyle='-', label=f"Channel {channel_number}")
    ax_polar.set_title(f"Channel {channel_number}")
    ax_polar.set_theta_zero_location("N")  # Set 0 degrees to North
    ax_polar.set_theta_direction(-1)  # Set clockwise direction
    ax_polar.set_xticks(np.linspace(0, 2 * np.pi, 18, endpoint=False))  # Set angle ticks
    ax_polar.set_xlabel("Angle (degrees)")
    ax_polar.set_ylabel("RMS Value dB", position=(0, 1), ha='left')
    ax_polar.set_rlabel_position(0)
    ax_polar.set_yticks(np.arange(-30, 3, 2))  # Set y-ticks

    # Plot RMS values in linear plot
    ax_linear.plot(angles, rms_values_norm_db, marker='.', linestyle='-', label=f"Channel {channel_number}")
    ax_linear.set_xlabel("Angle (degrees)")
    ax_linear.set_xticks(np.linspace(0, 380, 19, endpoint=False))  # Set angle ticks
    ax_linear.set_ylabel("RMS Value dB")
    ax_linear.set_yticks(np.arange(-30, 5, 2))  # Set y-ticks
    ax_linear.legend()
    ax_linear.grid(True)

fig_polar.tight_layout()
plt.show(block = False)
No description has been provided for this image
No description has been provided for this image
  1. Frequency analysis of every channel up to 40 KHz
InΒ [11]:
import soundfile as sf
from scipy import fft
# Central frequencies of the bands
central_freq = np.array([4e3, 6e3, 8e3, 10e3, 12e3, 14e3, 16e3, 18e3, 20e3, 22e3])
BW = 1e3  # Bandwidth of the bands
linestyles = ["-", "--", "-.", ":", "-", "--"]  # Line styles for the plot
# Group central frequencies into 3 sets of 6 bands each
num_bands_per_plot = 6
central_freq_sets = [central_freq[i * num_bands_per_plot:(i + 1) * num_bands_per_plot] for i in range(3)]
# Number of microphones
num_mics = num_channels
# Plot for each microphone
for mic in range(1, num_mics + 1):
    files = grouped_files[mic]
    angles = [int(file.split('_')[0]) for file in files]  # Extract angles from filenames
    # Create a figure with 3 polar subplots
    fig, axes = plt.subplots(1, 2, subplot_kw={"projection": "polar"}, figsize=(10, 6))
    plt.suptitle(f"Polar Frequency Response - Microphone {mic}", fontsize=20)
    for ax_idx, ax in enumerate(axes):
        ii = 0
        for fc in central_freq_sets[ax_idx]:
        
            audio_patt = []
            for file in files:
                file_path = os.path.join(DIR_first_sweep, file)
                audio, fs = sf.read(file_path)
                # Compute FFT
                audio_freq = fft.fft(audio, n=2048)
                audio_freq = audio_freq[:1024]
                freqs = fft.fftfreq(2048, 1 / fs)[:1024]
                # Compute mean radiance in the band
                band_mean = np.mean(np.abs(audio_freq[(freqs > fc - BW) & (freqs < fc + BW)]))
                audio_patt.append(band_mean)
            # Normalize and plot
            audio_patt_norm = audio_patt / audio_patt[0] # Normalize the radiance
            audio_patt_norm_dB = 20 * np.log10(audio_patt_norm) # Convert the radiance to dB
        
            if fc >= 10e3:
                label = f"{fc / 1e3:.0f} kHz"
            else:
                label = f"{fc / 1e3:.0f} kHz"
        
            ax.plot(np.deg2rad(angles), audio_patt_norm_dB, label=label, linestyle=linestyles[ii])
            ii +=1
        # Configure polar plot
        ax.legend(loc="upper right")
        ax.set_theta_offset(np.pi / 2)
        ax.set_theta_zero_location("N")  # Set 0 degrees to North
        ax.set_theta_direction(-1)  # Set clockwise direction
        ax.set_xticks(np.linspace(0, 2 * np.pi, 18, endpoint=False))  # Set angle ticks
        ax.set_yticks(np.linspace(-40, 0, 7))
        ax.set_xlabel("Angle (degrees)")
        ax.set_ylabel("RMS Value dB", position=(0, 1), ha='left')
        ax.set_rlabel_position(0)
    plt.tight_layout()
    plt.show()
No description has been provided for this image
No description has been provided for this image
No description has been provided for this image
No description has been provided for this image
No description has been provided for this image

Final notesΒΆ